﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;

namespace GBricks.Collections
{
  partial class Comparers
  {
    public static IEqualityComparer<T> Create<T>(Func<T, T, bool> equals, Func<T, int> hashCode) {
      Argument.NotNull(equals, "equals");
      Argument.NotNull(hashCode, "hashCode");
      Contract.Ensures(Contract.Result<IEqualityComparer<T>>() != null);

      return new MethodEqualityComparer<T>(equals, hashCode);
    }

    [SuppressMessage("Microsoft.Contracts", "Ensures", Justification = Justifications.CodeContractsIssue)]
    public static IComparer<T> Create<T>(Func<T, T, int> compare) {
      Argument.NotNull(compare, "compare");
      Contract.Ensures(Contract.Result<IComparer<T>>() != null);
      return Comparer<T>.Create((x, y) => compare(x, y));
    }

    public static IEqualityComparer<TOutput> Convert<TInput, TOutput>(this IEqualityComparer<TInput> comparer, Func<TOutput, TInput> converter) {
      Argument.NotNull(comparer, "comparer");
      Argument.NotNull(converter, "converter");
      Contract.Ensures(Contract.Result<IEqualityComparer<TOutput>>() != null);

      Func<TOutput, TOutput, bool> equals = (x, y) => comparer.Equals(converter(x), converter(y));
      Func<TOutput, int> hashCode = x => comparer.GetHashCode(converter(x));
      return Create(equals, hashCode);
    }

    public static IComparer<TOutput> Convert<TInput, TOutput>(this IComparer<TInput> comparer, Func<TOutput, TInput> converter) {
      Argument.NotNull(comparer, "comparer");
      Argument.NotNull(converter, "converter");
      Contract.Ensures(Contract.Result<IComparer<TOutput>>() != null);

      Func<TOutput, TOutput, int> comparison = (x, y) => comparer.Compare(converter(x), converter(y));
      return Create(comparison);
    }

    [Serializable]
    private sealed class MethodEqualityComparer<T> : EqualityComparer<T>
    {
      public MethodEqualityComparer(Func<T, T, bool> comparison, Func<T, int> hashCode) {
        Argument.NotNull(comparison, "comparison");
        Argument.NotNull(hashCode, "hashCode");

        Comparison = comparison;
        Hash = hashCode;
      }

      private Func<T, T, bool> Comparison { get; set; }
      private Func<T, int> Hash { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Comparison != null);
        Contract.Invariant(Hash != null);
      }

      public override bool Equals(T x, T y) {
        return Comparison(x, y);
      }

      public override int GetHashCode(T obj) {
        return Hash(obj);
      }

      public override bool Equals(object obj) {
        var other = obj as MethodEqualityComparer<T>;
        return other != null && other.Comparison == Comparison && other.Hash == Hash;
      }

      public override int GetHashCode() {
        return HashCode.Combine(Comparison.GetHashCode(), Hash.GetHashCode());
      }
    }
  }
}
